package com.mornd.gulimall.product.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mornd.gulimall.apis.coupon.ICouponClient;
import com.mornd.gulimall.apis.coupon.to.SeckillSkuRelationTo;
import com.mornd.gulimall.apis.coupon.to.SkuReductionTo;
import com.mornd.gulimall.apis.seckill.ISeckillClient;
import com.mornd.gulimall.common.core.result.AjaxResult;
import com.mornd.gulimall.common.core.result.PageConverter;
import com.mornd.gulimall.common.core.result.R;
import com.mornd.gulimall.common.core.utils.LocalDateTimeUtil;
import com.mornd.gulimall.product.entity.*;
import com.mornd.gulimall.product.entity.vo.SkuInfoVo;
import com.mornd.gulimall.product.entity.vo.skuitem.GroupNamesWithAttrs;
import com.mornd.gulimall.product.entity.vo.skuitem.ItemSales;
import com.mornd.gulimall.product.entity.vo.skuitem.SkuItemResult;
import com.mornd.gulimall.product.entity.vo.spusave.ImageVo;
import com.mornd.gulimall.product.entity.vo.spusave.SkuVo;
import com.mornd.gulimall.product.mapper.SkuInfoMapper;
import com.mornd.gulimall.product.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.mornd.gulimall.common.core.utils.BigDecimals.isValid;

/**
 * <p>
 * sku信息 服务实现类
 * </p>
 *
 * @author mornd
 * @since 2023-07-23
 */

@Slf4j
@Service
public class SkuInfoServiceImpl extends ServiceImpl<SkuInfoMapper, SkuInfo> implements ISkuInfoService {
    @Resource
    private ISkuImagesService skuImagesService;
    @Resource
    private ISkuSaleAttrValueService skuSaleAttrValueService;
    @Resource
    private ICouponClient couponClient;
    @Resource
    private ICategoryService categoryService;
    @Resource
    private IBrandService brandService;
    @Resource
    private ISpuInfoDescService spuInfoDescService;
    @Resource
    private IAttrService attrService;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Resource
    private ISeckillClient seckillClient;

    /**
     * 保存 sku 集合
     *
     * @param spu
     * @param skus
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSkus(SpuInfo spu, List<SkuVo> skus) {
        List<SkuInfo> saves = new ArrayList<>();
        for (SkuVo sku : skus) {
            // 保存自身 sku 信息
            SkuInfo skuInfo = new SkuInfo();
            BeanUtils.copyProperties(sku, skuInfo);

            skuInfo.setSpuId(spu.getId());
            skuInfo.setCatalogId(spu.getCatalogId());
            skuInfo.setBrandId(spu.getBrandId());
            skuInfo.setSaleCount(0L);

            // 获取 sku 默认图片
            List<ImageVo> images = sku.getSelectedImages();
            if (CollUtil.isNotEmpty(images)) {
                boolean flag = false;
                for (ImageVo image : images) {
                    // 只能有一张默认图片
                    if (image.getDefaultImg() == 1) {
                        if (flag) {
                            image.setDefaultImg(0);
                            continue;
                        }
                        skuInfo.setSkuDefaultImg(image.getUrl());
                        flag = true;
                    }
                }
                if (!flag) {
                    images.get(0).setDefaultImg(1);
                    skuInfo.setSkuDefaultImg(images.get(0).getUrl());
                }
            }

            saves.add(skuInfo);
        }

        super.saveBatch(saves);

        // 保存 sku 图集
        skuImagesService.saveSkuImgs(saves);

        // 保存销售属性 sales
        skuSaleAttrValueService.saveSales(saves);

        // 保存 sku 的优惠，满减，会员 信息
        List<SkuReductionTo> reductionTos = new ArrayList<>();
        for (int i = 0; i < skus.size(); i++) {
            SkuVo skuVo = skus.get(i);
            if (skuVo.getFullCount() > 0 || isValid(skuVo.getDiscount())
                    || isValid(skuVo.getFullPrice()) || isValid(skuVo.getReducePrice())
                    || CollUtil.isNotEmpty(skuVo.getMemberPrice())) {
                SkuReductionTo skuReductionTo = new SkuReductionTo();
                BeanUtils.copyProperties(skus.get(i), skuReductionTo);
                skuReductionTo.setSkuId(saves.get(i).getSkuId());

                reductionTos.add(skuReductionTo);
            }
        }

        if (!reductionTos.isEmpty()) {
            try {
                AjaxResult result = couponClient.saveSkuReductions(reductionTos);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public IPage pageList(SkuInfoVo vo) {
        IPage<SkuInfo> page = new Page<>(vo.validPageNum(), vo.validPageSize());

        LambdaQueryWrapper<SkuInfo> qw = Wrappers.lambdaQuery();
        qw.eq(vo.getCatalogId() != null, SkuInfo::getCatalogId, vo.getCatalogId());
        qw.eq(vo.getBrandId() != null, SkuInfo::getBrandId, vo.getBrandId());
        qw.and(StringUtils.hasText(vo.getKey()), (wrapper) -> {
            wrapper.eq(SkuInfo::getSkuId, vo.getKey())
                    .or().like(SkuInfo::getSkuName, vo.getKey());
        });

        if (isValid(vo.getMinPrice()) && isValid(vo.getMaxPrice())) {
            if (vo.getMinPrice().compareTo(vo.getMaxPrice()) > 0) {
                vo.setMaxPrice(null);
            }
        }

        qw.ge(isValid(vo.getMinPrice()), SkuInfo::getPrice, vo.getMinPrice());
        qw.le(isValid(vo.getMaxPrice()), SkuInfo::getPrice, vo.getMaxPrice());

        super.page(page, qw);

        List<SkuInfoVo> voList = new ArrayList<>();
        for (SkuInfo record : page.getRecords()) {
            SkuInfoVo skuInfoVo = new SkuInfoVo(record);

            Optional<Category> category = categoryService.getCacheById(record.getCatalogId());
            category.ifPresent(cat -> skuInfoVo.setCatalogName(cat.getName()));

            if (record.getBrandId() != null) {
                Brand brand = brandService.getById(record.getBrandId());
                if (brand != null) {
                    skuInfoVo.setBrandName(brand.getName());
                }
            }

            voList.add(skuInfoVo);
        }

        return PageConverter.convert(page, voList);
    }

    /**
     * 商品详情
     *
     * @param skuId
     * @return
     */
    @Override
    public SkuItemResult item(Long skuId) throws Exception {
        log.info("sku item 开始执行...");
        SkuItemResult result = new SkuItemResult();

        /*
            CompletableFuture 异步编排
            执行步骤：
            1、进入该方法
            2、并行执行 cf1，cf5
            3、cf2、3、4 等待 cf1 执行完毕后，并行执行
            4、等待 cf2、3、4、5 执行完毕，返回结果

            打印：如果cf1耗时2s，cf5耗时1s，cf2、3、4耗时各1s
            00:00:00    sku item 开始执行...
            00:00:00    sku item 执行完毕，等待汇总...
            00:00:00    开始查询sku基本信息
            00:00:00    开始查询sku图片信息(不依赖sku结果)

            00:00:01    sku图片信息查询结束

            00:02:00    sku基本信息查询结束，结果：com.mornd.gulimall.product.entity.SkuInfo@400afab

            00:02:00    开始查询sku基本属性
            00:02:00    开始查询sku销售属性
            00:02:00    开始查询sku介绍图片信息

            00:02:01    sku销售属性查询结束
            00:02:01    sku基本属性查询结束
            00:02:01    sku介绍图片信息查询结束

            00:02:01    sku item 汇总完毕，返回结果...

            thenAcceptAsync 和 thenAccept 区别
            thenAcceptAsync(r)
                                        \
                                            该方法会启用一个新的线程来处理，不指定线程池就用cf默认ForkJoinPool线程池
            thenAcceptAsync(r, pool)    /

            thenAccept 该方法在当前线程中执行，即在调用 cf1 的线程中执行，不会启用新的线程
         */
        CompletableFuture<SkuInfo> cf1 = CompletableFuture.supplyAsync(() -> {
            // 查询基本信息
            log.debug("开始查询sku基本信息");
            SkuInfo sku = super.getById(skuId);
            if (sku.getBrandId() != null) {
                String brandName = brandService.getNameById(sku.getBrandId());
                sku.setBrandName(brandName);
            }
            log.debug("sku基本信息查询结束，结果：{}", sku.toString());
            result.setInfo(sku);
            return sku;
        }, threadPoolTaskExecutor);

//        if ((skuInfo = cf1.get()) == null) {
//            return result;
//        }

        // cf2，3，4 都需要等待cf1执行完毕执行，然后异步执行各个任务
        CompletableFuture<Void> cf2 = cf1.thenAcceptAsync((r) -> {
            if (r == null) return;
            // 销售属性
            log.debug("开始查询sku销售属性");
            List<ItemSales> saleAttrs = skuSaleAttrValueService.getSalesBySpuId(r.getSpuId());
            result.setSaleAttrs(saleAttrs);
            log.debug("sku销售属性查询结束");
        }, threadPoolTaskExecutor);

        CompletableFuture<Void> cf3 = cf1.thenAcceptAsync((r) -> {
            if (r == null) return;
            // 基本属性
            log.debug("开始查询sku基本属性");
            List<GroupNamesWithAttrs> groups = attrService.getGroupsAndAttrsBySpuId(r.getSpuId());
            result.setGroupBaseAttrs(groups);
            log.debug("sku基本属性查询结束");
        }, threadPoolTaskExecutor);

        CompletableFuture<Void> cf4 = cf1.thenAcceptAsync((r) -> {
            if (r == null) return;
            // spu 介绍图片
            log.debug("开始查询sku介绍图片信息");
            Optional<SpuInfoDesc> desc = spuInfoDescService.getBySpuId(r.getSpuId());
            desc.ifPresent(it -> result.setSpuDescImages(Arrays.asList(it.getDecript().split(","))));
            log.debug("sku介绍图片信息查询结束");
        }, threadPoolTaskExecutor);

        // 不用等待sku基本信息查询完既可执行
        CompletableFuture<Void> cf5 = CompletableFuture.runAsync(() -> {
            // sku 图片
            log.debug("开始查询sku图片信息(不依赖sku结果)");
            List<SkuImages> skuImages = skuImagesService.geyBySkuId(skuId);
            result.setImages(skuImages.stream().map(SkuImages::getImgUrl).collect(Collectors.toList()));
            log.debug("sku图片信息查询结束");
        }, threadPoolTaskExecutor);

        log.info("sku item 执行完毕，等待汇总...");

        // 阻塞等待2、3、4、5任务执行完毕才继续玩下走，无需等待cf1，是因为cf2执行完cf1必定执行完了
        CompletableFuture.allOf(cf2, cf3, cf4, cf5).join();

        log.info("sku item 汇总完毕，返回结果...");

        // 查询该商品的秒杀相关信息
        try {
            R<SeckillSkuRelationTo> r = seckillClient.getSkuSeckillInfo(skuId);
            SeckillSkuRelationTo secData = r.getData();
            if (r.isOk() && secData != null) {
                result.setSeckillInfo(secData);

                // 生成提示信息
                long now = LocalDateTimeUtil.toEpochMilli(LocalDateTime.now());
                if (now < secData.getSeckillStartTime()) {
                    LocalDateTime of = LocalDateTimeUtil.of(secData.getSeckillStartTime());
                    String format = LocalDateTimeUtil.format(of, "yyyy-MM-dd HH:mm:ss");

//                    Date start = new Date(secData.getSeckillStartTime());
//                    String format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(start);
                    result.setTips("秒杀活动将会在 " + format + " 进行！");
                } else {
                    result.setSeckillArea(true);
                    result.setTips("活动已开始，秒杀价：￥ " + secData.getSeckillPrice());
                }

            } else {
                log.error(r.getMsg());
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }

        return result;
    }
}
